package orm

import (
	"fmt"
	"reflect"
	"strings"
)

// 数据集
type Dataset struct {
	tx    *Tx
	name  string
	alias string
	cond  Cond
	links map[string]*Dataset
}

// 创建数据集实例
func newDataset(tx *Tx, name string) *Dataset {
	return &Dataset{
		tx:    tx,
		name:  name,
		alias: fmt.Sprintf("t%v", tx.SerNo()),
		links: map[string]*Dataset{},
	}
}

// 复制一个数据集
func cloneDataset(ds *Dataset) *Dataset {
	x := &Dataset{
		tx:    ds.tx,
		name:  ds.name,
		alias: fmt.Sprintf("t%v", ds.tx.SerNo()),
		cond:  ds.cond,
		links: map[string]*Dataset{},
	}
	for k, v := range ds.links {
		x.links[k] = v
	}
	return x
}

// 过滤
func (ds *Dataset) Filter(c Cond) *Dataset {
	x := cloneDataset(ds)
	if x.cond == nil {
		x.cond = c
	} else {
		x.cond = And(x.cond, c)
	}
	return x
}

// 链接
func (ds *Dataset) Link(field string, t *Dataset) *Dataset {
	x := cloneDataset(ds)
	x.links[field] = t
	return x
}

// 插入数据
func (ds *Dataset) Insert(x interface{}, fields ...string) (int64, error) {
	id := genId()
	n := len(fields)
	v := reflect.ValueOf(x).Elem()
	adaptor := ds.tx.db.adaptor

	cols := make([]string, n+1)
	args := make([]interface{}, n+1)
	params := make([]string, n+1)

	for i := 0; i < n; i++ {
		cols[i] = adaptor.Column(underscore(fields[i]))
		params[i] = adaptor.Param(i)

		f := v.FieldByName(fields[i])
		if isAtom(f.Type()) {
			args[i] = f.Interface()
		} else if isPrt(f.Type()) {
			args[i] = f.Elem().FieldByName("Id").Interface()
		} else {
			panic(ErrBadType)
		}
	}
	cols[n], args[n], params[n] = adaptor.Column("id"), id, adaptor.Param(n)

	err := ds.tx.Exec(fmt.Sprintf("INSERT INTO %v(%v) VALUES(%v)",
		ds.name,
		strings.Join(cols, ","),
		strings.Join(params, ",")), args...)
	return id, err
}

// 删除数据
func (ds *Dataset) Delete() error {
	if ds.cond == nil {
		return ds.tx.Exec(fmt.Sprintf("DELETE FROM %v", ds.name))
	} else {
		adaptor := ds.tx.db.adaptor
		i, args := 0, []interface{}{}
		where := ds.cond.Gen(&args, func() string {
			p := adaptor.Param(i)
			i += 1
			return p
		}, func(field string) string {
			return adaptor.Column(underscore(field))
		})
		return ds.tx.Exec(fmt.Sprintf("DELETE FROM %v WHERE %v", ds.name, where), args...)
	}
}

// 更新数据
func (ds *Dataset) Update(x interface{}, fields ...string) error {
	n := len(fields)
	v := reflect.ValueOf(x).Elem()
	adaptor := ds.tx.db.adaptor

	cols := make([]string, n)
	args := make([]interface{}, n)
	params := make([]string, n)

	for i := 0; i < n; i++ {
		cols[i] = adaptor.Column(underscore(fields[i]))
		params[i] = fmt.Sprintf("%v=%v", cols[i], adaptor.Param(i))

		f := v.FieldByName(fields[i])
		if isAtom(f.Type()) {
			args[i] = f.Interface()
		} else if isPrt(f.Type()) {
			args[i] = f.Elem().FieldByName("Id").Interface()
		} else {
			panic(ErrBadType)
		}
	}

	if ds.cond == nil {
		return ds.tx.Exec(
			fmt.Sprintf("UPDATE %v SET %v",
				ds.name,
				strings.Join(params, ",")),
			args...)
	} else {
		i := n
		where := ds.cond.Gen(&args, func() string {
			p := adaptor.Param(i)
			i += 1
			return p
		}, func(field string) string {
			return adaptor.Column(underscore(field))
		})
		return ds.tx.Exec(
			fmt.Sprintf("UPDATE %v SET %v WHERE %v",
				ds.name,
				strings.Join(params, ","),
				where),
			args...)
	}
}

// 查询数据
func (ds *Dataset) Select(x interface{}) error {
	t := elemType(x)
	args := []interface{}{}
	cols := []string{}
	joins := []string{}
	conds := []string{}

	pidx := 0
	adaptor := ds.tx.db.adaptor
	ds.genQuery(t, func() string {
		p := adaptor.Param(pidx)
		pidx += 1
		return p
	}, &args, &cols, &joins, &conds)

	var query string
	if len(conds) > 0 {
		query = fmt.Sprintf("SELECT %v FROM %v %v %v WHERE %v",
			strings.Join(cols, ","),
			ds.name,
			ds.alias,
			strings.Join(joins, " "),
			strings.Join(conds, " AND "))
	} else {
		query = fmt.Sprintf("SELECT %v FROM %v %v %v",
			strings.Join(cols, ","),
			ds.name,
			ds.alias,
			strings.Join(joins, " "))
	}

	et := reflect.TypeOf(x).Elem()
	if isStruct(et) {
		return ds.selectOne(query, args, x)
	} else if isSlice(et) {
		return ds.selectAll(query, args, x)
	} else {
		panic(ErrBadType)
	}
}

func (ds *Dataset) selectAll(query string, args []interface{}, x interface{}) error {
	t := elemType(x)
	vs := reflect.ValueOf(x).Elem()

	rows, err := ds.tx.Query(query, args...)
	if err != nil {
		return err
	}
	for rows.Next() {
		fs := []interface{}{}
		v := reflect.New(t)
		ds.genFields(v.Interface(), &fs)

		err := rows.Scan(fs...)
		if err != nil {
			rows.Close()
			return err
		}
		vs.Set(reflect.Append(vs, v))
	}
	return rows.Close()
}

func (ds *Dataset) selectOne(query string, args []interface{}, x interface{}) error {
	row := ds.tx.QueryRow(query, args...)
	fs := []interface{}{}
	ds.genFields(x, &fs)
	return row.Scan(fs...)
}

// 生成SELECT SQL
func (ds *Dataset) genQuery(t reflect.Type, param func() string, args *[]interface{}, cols *[]string, joins *[]string, conds *[]string) {
	if !isStruct(t) {
		panic(ErrBadType)
	}
	adaptor := ds.tx.db.adaptor

	// 生成WHERE条件
	if ds.cond != nil {
		*conds = append(*conds, fmt.Sprintf("(%v)", ds.cond.Gen(args, param, func(field string) string {
			return fmt.Sprintf("%v.%v", ds.alias, adaptor.Column(underscore(field)))
		})))
	}

	n := t.NumField()
	for i := 0; i < n; i++ {
		f := t.Field(i)
		if isAtom(f.Type) {
			*cols = append(*cols, fmt.Sprintf("%v.%v", ds.alias, adaptor.Column(underscore(f.Name))))
		} else if isPrt(f.Type) {
			if lds, ok := ds.links[f.Name]; ok {
				*joins = append(*joins, fmt.Sprintf("LEFT JOIN %v %v ON %v.%v=%v.%v",
					lds.name, lds.alias, lds.alias, adaptor.Column("id"), ds.alias, adaptor.Column(underscore(f.Name))))
				lds.genQuery(f.Type.Elem(), param, args, cols, joins, conds)
			} else {
				*cols = append(*cols, fmt.Sprintf("%v.%v", ds.alias, adaptor.Column(underscore(f.Name))))
			}
		}
	}
}

// 生成字段列表
func (ds *Dataset) genFields(x interface{}, fs *[]interface{}) {
	v := reflect.ValueOf(x).Elem()
	t := v.Type()

	n := t.NumField()
	for i := 0; i < n; i++ {
		ft, fv := t.Field(i), v.Field(i)

		if isAtom(ft.Type) {
			*fs = append(*fs, fv.Addr().Interface())
		} else if isPrt(ft.Type) {
			el := reflect.New(ft.Type.Elem())
			fv.Set(el)
			if lds, ok := ds.links[ft.Name]; ok {
				lds.genFields(el.Interface(), fs)
			} else {
				*fs = append(*fs, el.Elem().FieldByName("Id").Addr().Interface())
			}
		}
	}
}
